package cn.hyy.hrm.service.impl;

import cn.hutool.core.util.ZipUtil;
import cn.hyy.basic.util.AjaxResult;
import cn.hyy.hrm.domain.CourseType;
import cn.hyy.hrm.RedisClient;
import cn.hyy.hrm.client.PageClient;
import cn.hyy.hrm.client.FileClient;
import cn.hyy.hrm.controller.vo.CrumbVo;
import cn.hyy.hrm.mapper.*;
import cn.hyy.hrm.service.ICourseTypeService;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import java.io.*;
import java.nio.file.Files;
import java.util.*;


/**
 * <p>
 * 课程目录 服务实现类
 * </p>
 *
 * @author hyy
 * @since 2020-04-02
 */
@Service
public class CourseTypeServiceImpl extends ServiceImpl<CourseTypeMapper, CourseType> implements ICourseTypeService {

    private Logger logger = LoggerFactory.getLogger(CourseTypeServiceImpl.class);

    @Autowired
    private RedisClient redisClient;

    private final String KEY = "courseTypes";

    @Autowired
    private PageClient pageClient;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private FileClient fileClient;


    /**
     * 加载类型面包屑
     *  //查询当前级别和当前级别的兄弟类型
     //查询当前级别的父类型和父类型的兄弟类型
     //查询当前级别的爷爷类型和爷爷类型的兄弟类型
     //将数据封装到List中返回
     上面的方式实现起来 1、代码繁琐，2、如果不能确定类型级别，使用这种方式就不香了
     * @param productTypeId
     * @return
     */
    @Override
    public List<CrumbVo> loadCrumbs(Long productTypeId) {

        List<CrumbVo> crumbVoList = new ArrayList<>();

        //只要能够查询出各个级别的商品类型就可以了  -  path字段
        CourseType courseType = baseMapper.selectById(productTypeId);
        String path = courseType.getPath();//   .1037.1039.1040.

        //解析Path，获取出各个级别的课程类型
        path = path.substring(1);
        String[] ids = path.split("\\.");
        List<CourseType> courseTypes = baseMapper.selectBatchIds(Arrays.asList(ids));

        for (CourseType type : courseTypes) {

            CrumbVo vo = new CrumbVo();
            vo.setCurrentType(type);
            //当前级别的其他类型
            List<CourseType> otherTypes = baseMapper.selectList(new QueryWrapper<CourseType>().eq("pid", type.getPid()));
            vo.setOtherTypes(otherTypes);

            crumbVoList.add(vo);

        }

        return crumbVoList;
    }

    public static void main(String[] args) {

        String path = ".1037.1039.1040.";
        path = path.substring(1);

        //split参数为正则表达式，点“.”在正则表达式中有特殊含义，表示匹配任意一个字符
        //需要对.进行转义
        String[] split = path.split("\\.");
        System.out.println(Arrays.toString(split));
    }


    /**
     * 课程首页页面静态化
     */
    @Override
    public void staticHomepage() {

        //上传模板到fastdfs中
        //打包模板
        String sourceDir = this.getClass().getResource("/").getPath()+ File.separator+"/templates/home";
        logger.debug("sourceDir="+sourceDir);
        String targetDir = this.getClass().getResource("/").getPath()+ File.separator+"/tmp";
        logger.debug("targetDir="+targetDir);
        ZipUtil.zip(sourceDir,targetDir+"/home.zip",false);

        InputStream input = null;
        OutputStream os = null;
        try {
            //File转成MultipartFile
            File f = new File(targetDir+"/home.zip");
            FileItem fileItem = new DiskFileItem("file", Files.probeContentType(f.toPath()), false, f.getName(), (int) f.length(), f.getParentFile());
            input = new FileInputStream(f);
            os = fileItem.getOutputStream();
            IOUtils.copy(input, os);
            MultipartFile multipartFile = new CommonsMultipartFile(fileItem);
            logger.debug("--------multipartFile-------------"+multipartFile);

            AjaxResult uploadAjax = fileClient.upload(multipartFile);
            logger.debug("----------getResultObj-----------"+uploadAjax.getResultObj());
            String fileId = (String) uploadAjax.getResultObj();
            logger.debug("fileId="+fileId);
            //封装模板的file和data
            Map<String,Object> data = new HashMap<>();
            //课程类型放入data
            List<CourseType> courseTypes = loadDataTree();
            logger.debug("----------courseTypes-------"+courseTypes);
            data.put("courseTypes",courseTypes);


            //调用页面静态化接口
            AjaxResult ajaxResult = pageClient.staticPage(fileId,data);
            logger.debug("*******ajaxResult="+ajaxResult.toString());
            Object pageFileId = ajaxResult.getResultObj();

            logger.debug("pageFileId="+pageFileId);

            //将pageFileId作为消息发送到rabbitmq中
//            rabbitTemplate.convertAndSend(RabbitMqConfig.EXCHANGE_DIRECT_INFORM,RabbitMqConfig.ROUTING_KEY,pageFileId);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            try {
                if(os!=null) {
                    os.close();
                }
                if(input!=null){
                    input.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }



    /**
     * 加载课程类型树
     *
     *    无限级别？？？
     *
     * 从缓存中获取数据
     *     课程类型树的数据是一个List
     *     如何将这个List数据添加到redis中
     *     redis中的value我们应该选择什么数据类型？？？
     *          string,list,set,sortedset,hash
     *
     *          list可以选择，但是通常是经常修改的数据在redis中使用list存储
     *          我们选择string或者byte[] 字节数组（序列化）的形式
     *
     * 面试题(*******)：
     * java中的对象如果存到redis中，选择什么类型的value合适？？
     * (1)string -> 将java对象转成json字符串，保持到redis中，从redis中查询出来，再将json字符串转回java对象可以继续使用了
     * (2)序列化 -> 如果对象比较大，并且里面有很多的属性之外的方法要保存，json字符串肯定是不合适的，我们可以选择将对象序列化成字节数组保存到redis中
     *              从redis中查询除字节数组，再反序列化成java对象就可以继续使用了（session共享问题）
     *
     * 这里我们选择string，如果json字符串比较长，可以继续序列化成byte[]
     * @return
     */
    @Override
    public List<CourseType> loadDataTree() {
        //1、从缓存中获取数据
        AjaxResult result = redisClient.getStr(KEY);
        if(!result.isSuccess()){
            return null;
        }
        //2、判断缓存中的数据是否存在
        String courseTypes = (String) result.getResultObj();
        if(StringUtils.isEmpty(courseTypes)){
            //缓存不存在
            synchronized (this){
                result = redisClient.getStr(KEY);
                if(!result.isSuccess()){
                    return null;
                }
                courseTypes = (String) result.getResultObj();
                if(StringUtils.isEmpty(courseTypes)){
                    logger.debug("缓存中没有数据,查询数据库并缓存......");
                    List<CourseType> courseTypeList = null;
                    //4、如果不存在，则查询数据库，将查询结果缓存到redis中
                    courseTypeList = loopMapCourseTypes();

                    String jsonString = JSONArray.toJSONString(courseTypeList);
                    redisClient.setStr(KEY,jsonString);
                    //5、再将结果返回
                    return courseTypeList;
                }
            }
        }

        logger.debug("从缓存中获取到了数据......");
        //3、如果存在，则将缓存中获取的数据直接返回 - fastjson
        List<CourseType> courseTypeList = JSONArray.parseArray(courseTypes, CourseType.class);
        return courseTypeList;
    }

    /**
     * 第一种方式 递归
     *      缺点：
     *          效率低，每一次都要去查询数据库
     *          代码可读性差
     *          每次递归，方法栈都会添加一个栈帧，容易导致栈溢出
     * @param pid
     * @return
     */
    private List<CourseType> listChildrenByPid(Long pid){
        //查询到pid所有的子类型
        List<CourseType> CourseTypes =
                baseMapper.selectList(new QueryWrapper<CourseType>().eq("pid",pid));

        //递归的出口，没有子类型的时候直接返回空集合
        if(CourseTypes==null||CourseTypes.size()<=0){
            return new ArrayList<>();// 建议不要return null
        }

        //向每一个子类行中添加children
        for (CourseType CourseType : CourseTypes) {
            List<CourseType> children = listChildrenByPid(CourseType.getId());
            CourseType.setChildren(children);
        }

        return CourseTypes;
    }
    /**
     * 第二种
     *      循环(嵌套循环)
     */
    private List<CourseType> loopCourseTypes(){

        //创建一个List存放结果（一级类型）
        List<CourseType> coursesTypes = new ArrayList<>();

        //一次性查询出所有的类型数据
        List<CourseType> allCoursetTypes = baseMapper.selectList(null);

        //嵌套循环
        for (CourseType courseType : allCoursetTypes) {

            if(courseType.getPid()==0){
                //如果是一级类型，直接添加到结果里面去
                coursesTypes.add(courseType);
            }else{
                //如果不是一级类型，则找到对应的父类型，添加到父类型的children属性中
                for (CourseType parentType : allCoursetTypes) {
                    if(courseType.getPid().longValue() == parentType.getId().longValue()){
                        parentType.getChildren().add(courseType);
                    }
                }
            }
        }

        return coursesTypes;
    }

    /**
     * 查询无限的课程类型 - 循环+Map
     * @return
     */
    private List<CourseType> loopMapCourseTypes(){

        //创建一个List存放结果（一级类型）
        List<CourseType> coursesTypes = new ArrayList<>();

        //一次性查询出所有的类型数据
        List<CourseType> allCoursetTypes = baseMapper.selectList(null);

        //将所有类型放入map中，key为类型的id，value为当前类型对象
        Map<Long,CourseType> courseTypeMap = new HashMap<>();
        for (CourseType coursetType : allCoursetTypes) {
            courseTypeMap.put(coursetType.getId(),coursetType);
        }


        //嵌套循环
        for (CourseType courseType : allCoursetTypes) {

            if(courseType.getPid()==0){
                //如果是一级类型，直接添加到结果里面去
                coursesTypes.add(courseType);
            }else{
                //如果不是一级类型，则找到对应的父类型，添加到父类型的children属性中
                CourseType parent = courseTypeMap.get(courseType.getPid());
                if(parent!=null){
                    parent.getChildren().add(courseType);
                }
            }
        }

        return coursesTypes;
    }


   @Override
    public boolean save(CourseType entity) {
        super.save(entity);
        SynchronizedOperate();
        return false;
    }

    @Override
    public boolean removeById(Serializable id) {
        super.removeById(id);
        SynchronizedOperate();
        return false;
    }

    @Override
    public boolean updateById(CourseType entity) {
        super.updateById(entity);
        SynchronizedOperate();
        return false;
    }

    /**
     * 增删改得同步操作
     */
    public void SynchronizedOperate(){
        logger.debug("修改数据redis同步数据");
        //从数据库中获取课程类型树，重新设置到redis缓存中
        List<CourseType> types = loopMapCourseTypes();
        String s = JSONArray.toJSONString(types);
        redisClient.setStr(KEY, s);
        //同步操作重新对课程首页进行页面静态化操作
        staticHomepage();
    }
}
